package com.aco.fatureapp.service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.collections4.IteratorUtils;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import android.annotation.SuppressLint;
import android.database.sqlite.SQLiteDatabase;
import br.com.ac.fatureweb.integration.billing.ContaVO;
import br.com.ac.fatureweb.integration.billing.LancamentoCobradoVO;
import br.com.ac.fatureweb.integration.billing.LogCorrecaoContaVO;
import br.com.ac.fatureweb.integration.billing.LogLeituraVO;
import br.com.ac.fatureweb.integration.billing.ProdutoVO;
import br.com.ac.fatureweb.integration.billing.ValorVO;
import br.com.ac.fatureweb.integration.billing.VolumeVO;
import br.com.ac.fatureweb.integration.enums.EnumSincronizacaoTabelas;

import com.aco.fatureapp.enums.EnumProgress;
import com.aco.fatureapp.model.Roteiro;
import com.aco.fatureapp.model.billing.Conta;
import com.aco.fatureapp.model.billing.LancamentoCobrado;
import com.aco.fatureapp.model.billing.LogCorrecaoConta;
import com.aco.fatureapp.model.billing.LogLeitura;
import com.aco.fatureapp.model.billing.Produto;
import com.aco.fatureapp.model.billing.Valor;
import com.aco.fatureapp.model.billing.Volume;
import com.aco.fatureapp.utilitary.JSonHttpRequest;
import com.aco.fatureapp.utilitary.JSonHttpRequest.EnumTipoRequisicao;
import com.aco.fatureapp.utilitary.ProgressAsyncTask;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.orm.SugarApp;
import com.orm.SugarDb;
import com.orm.query.Select;

@SuppressLint("UseSparseArrays")
public class SrvExportRoute {

	public static final String SEM_ROTEIRO_DISPONIVEL = "O roteiro atual não pode ser descarregado.";

	private List<NameValuePair> mParams = new ArrayList<NameValuePair>();
	private JSonHttpRequest mJsonRequest = null;
	private SQLiteDatabase mDatabase = null;
	private ProgressAsyncTask mTask = null;

	/*
	 * Se for preenchido, significa que o serviço estará exportando novamente
	 * dados de retorno que tiveram correção, através do encerramento.
	 */
	private String mWhereClause = null;

	@Inject
	public SrvExportRoute() {
		super();
		this.mJsonRequest = new JSonHttpRequest();
	}

	public String execute(final List<NameValuePair> mParams,
			final ProgressAsyncTask mTask, String mWhereClause)
			throws Exception {

		this.mDatabase = new SugarDb(SugarApp.getSugarContext())
				.getWritableDatabase();

		this.mTask = mTask;
		this.mParams = mParams;
		this.mWhereClause = mWhereClause;

		if (JSonHttpRequest
				.webServiceIsAvailable(JSonHttpRequest.REQUEST_TYPE_EXPORT)) {

			if (mTask != null) {
				mTask.myPublishProgress(EnumProgress.INICIO.getType());
			}

			if (executeValidations()) {

				gerarRetornoConta();

				gerarRetornoLancamentoCobrado();

				gerarRetornoLogCorrecaoConta();

				gerarRetornoLogLeitura();

				gerarRetornoProduto();

				gerarRetornoValor();

				gerarRetornoVolume();

				/*
				 * Enviando entidades de retorno.
				 */
				mJsonRequest
						.execute(JSonHttpRequest.getExportURL()
								+ "receiveRoute", EnumTipoRequisicao.POST,
								this.mParams);

				if (mWhereClause == null) {
					mTask.myPublishProgress(EnumProgress.FIM.getType());
				}

				return JSonHttpRequest.SYNC_SUCCESSFUL;
			}

			throw new Exception(SEM_ROTEIRO_DISPONIVEL);
		}

		throw new Exception(JSonHttpRequest.SERVICO_INDISPONIVEL);
	}

	/**
	 * 1. Recuperar os dados de conta de gerar um retorno no formato JSON.
	 */
	private void gerarRetornoConta() {

		Iterator<Conta> lstRetornoConta = null;

		if (mWhereClause != null) {
			lstRetornoConta = Select.from(Conta.class).where(mWhereClause)
					.iterator();
		}
		else {
			lstRetornoConta = Conta.findAll(Conta.class);
		}

		if (lstRetornoConta != null && lstRetornoConta.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoConta),
					new TypeToken<ArrayList<ContaVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoConta", json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 1, 1, 1,
						EnumSincronizacaoTabelas.Conta.getCodigo());
			}
		}
	}

	/**
	 * 2. Recuperar os dados de lançamentos e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoLancamentoCobrado() {
		Iterator<LancamentoCobrado> lstRetornoLancamentoCobrado = null;

		if (mWhereClause != null) {
			lstRetornoLancamentoCobrado = Select.from(LancamentoCobrado.class)
					.where(mWhereClause).iterator();
		}
		else {
			lstRetornoLancamentoCobrado = LancamentoCobrado
					.findAll(LancamentoCobrado.class);
		}

		if (lstRetornoLancamentoCobrado != null
				&& lstRetornoLancamentoCobrado.hasNext()) {

			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoLancamentoCobrado),
					new TypeToken<ArrayList<LancamentoCobradoVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoLancamentoCobrado",
					json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 2, 1, 1,
						EnumSincronizacaoTabelas.LancamentoCobrado.getCodigo());
			}
		}
	}

	/**
	 * 3. Recuperar os dados de correção e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoLogCorrecaoConta() {
		Iterator<LogCorrecaoConta> lstRetornoLogCorrecaoConta = null;

		if (mWhereClause != null) {
			lstRetornoLogCorrecaoConta = Select.from(LogCorrecaoConta.class)
					.where(mWhereClause).iterator();
		}
		else {
			lstRetornoLogCorrecaoConta = LogCorrecaoConta
					.findAll(LogCorrecaoConta.class);
		}

		if (lstRetornoLogCorrecaoConta != null
				&& lstRetornoLogCorrecaoConta.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoLogCorrecaoConta),
					new TypeToken<ArrayList<LogCorrecaoContaVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoLogCorrecaoConta",
					json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 3, 1, 1,
						EnumSincronizacaoTabelas.LogCorrecaoConta.getCodigo());
			}
		}
	}

	/**
	 * 4. Recuperar os dados de leitura e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoLogLeitura() {
		Iterator<LogLeitura> lstRetornoLogLeitura = null;

		if (mWhereClause != null) {
			lstRetornoLogLeitura = Select.from(LogLeitura.class)
					.where(mWhereClause).iterator();
		}
		else {
			lstRetornoLogLeitura = LogLeitura.findAll(LogLeitura.class);
		}

		if (lstRetornoLogLeitura != null && lstRetornoLogLeitura.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoLogLeitura),
					new TypeToken<ArrayList<LogLeituraVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoLogLeitura", json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 4, 1, 1,
						EnumSincronizacaoTabelas.LogLeitura.getCodigo());
			}
		}
	}

	/**
	 * 5. Recuperar os dados de produtos e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoProduto() {
		Iterator<Produto> lstRetornoProduto = null;

		if (mWhereClause != null) {
			lstRetornoProduto = Select.from(Produto.class).where(mWhereClause)
					.iterator();
		}
		else {
			lstRetornoProduto = Produto.findAll(Produto.class);
		}

		if (lstRetornoProduto != null && lstRetornoProduto.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoProduto),
					new TypeToken<ArrayList<ProdutoVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoProduto", json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 5, 1, 1,
						EnumSincronizacaoTabelas.Produto.getCodigo());
			}
		}
	}

	/**
	 * 6. Recuperar os dados de valor e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoValor() {
		Iterator<Valor> lstRetornoValor = null;

		if (mWhereClause != null) {
			lstRetornoValor = Select.from(Valor.class).where(mWhereClause)
					.iterator();
		}
		else {
			lstRetornoValor = Valor.findAll(Valor.class);
		}

		if (lstRetornoValor != null && lstRetornoValor.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoValor),
					new TypeToken<ArrayList<ValorVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoValor", json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 6, 1, 1,
						EnumSincronizacaoTabelas.Valor.getCodigo());
			}
		}
	}

	/**
	 * 7. Recuperar os dados de volume e gerar um retorno no formato JSON.
	 */
	private void gerarRetornoVolume() {
		Iterator<Volume> lstRetornoVolume = Volume.findAll(Volume.class);

		if (mWhereClause != null) {
			lstRetornoVolume = Select.from(Volume.class).where(mWhereClause)
					.iterator();
		}
		else {
			lstRetornoVolume = Volume.findAll(Volume.class);
		}

		if (lstRetornoVolume != null && lstRetornoVolume.hasNext()) {
			String json = new Gson().toJson(
					IteratorUtils.toList(lstRetornoVolume),
					new TypeToken<ArrayList<VolumeVO>>() {
					}.getType());

			mParams.add(new BasicNameValuePair("lstRetornoVolume", json));

			if (mWhereClause == null) {
				mJsonRequest.atualizarProgresso(mTask, 7, 1, 1,
						EnumSincronizacaoTabelas.Volume.getCodigo());
			}
		}
	}

	/**
	 * Recuperar as entidades de log leitura que ainda não foram retornadas pelo
	 * serviço de envio automático.
	 *
	 * @return
	 */
	public List<LogLeitura> getLogLeituraEnvioAutomatico() {
		return Select.from(LogLeitura.class)
				.where("FLG_ENVIO_AUTOMATICO IS NULL").list();
	}

	/**
	 * Executa as validação necessárias para verificar se o roteiro está apto
	 * para exportação.
	 *
	 * @return
	 * @throws Exception
	 */
	private Boolean executeValidations() throws Exception {
		// implements another validations here.
		long count = Roteiro.count(Roteiro.class, null, null);

		return count > 0;
	}

	/**
	 *
	 * Busca pelo roteiro atual.
	 *
	 * @return
	 */
	public Roteiro findRoteiro() {
		Iterator<Roteiro> lstRoteiro = Roteiro.findAll(Roteiro.class);

		if (lstRoteiro != null && lstRoteiro.hasNext()) {
			return lstRoteiro.next();
		}

		return null;
	}

	/**
	 * Método responsável por fechar a conexão com a base de dados.
	 */
	public void closeDB() {

		if (mDatabase != null) {
			mDatabase.close();
		}
	}

}
